/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "ExtraServices.h"
#include "svcReminder.h"
#include "dlgAnniversaryList.h"

#include "m_message.h"
#include "m_email.h"

namespace NAnniversaryList 
{
	#define IsLeap( wYear )	( !( (( wYear ) % 4 != 0 ) || ( (( wYear ) % 100 == 0 ) && ( ( wYear ) % 400 != 0 ) ) ) )

	class CAnnivList;

	static CAnnivList *gpDlg = NULL;

	/***********************************************************************************************************
	 * class CAnnivList
	 ***********************************************************************************************************/

	class CAnnivList
	{
		HWND		_hDlg;
		HWND		_hList;
		SIZE		_sizeMin;
		SHORT		_sortOrder;
		INT			_sortHeader;
		INT			_curSel;
		INT			_numRows;
		BYTE		_bRemindEnable;
		HANDLE	_mHookExit;

		typedef INT ( CALLBACK* CMPPROC )( LPARAM, LPARAM  LPARAM );  

		enum EColumn 
		{
			COLUMN_ETA = 0,
			COLUMN_CONTACT,
			COLUMN_PROTO,
			COLUMN_AGE,
			COLUMN_DESC,
			COLUMN_DATE,
		};

		enum EFilter 
		{
			FILTER_ALL = 0,
			FILTER_BIRTHDAY,
			FILTER_ANNIV,
			FILTER_DISABLED_REMINDER
		};

		struct CFilter 
		{
			WORD		wDaysBefore;
			BYTE		bFilterIndex;
			LPSTR		pszProto;
			LPTSTR	pszAnniv;

			CFilter() 
			{
				wDaysBefore = ( WORD )-1;
				bFilterIndex = 0;
				pszProto = NULL;
				pszAnniv = NULL;
			}
		} _filter;

		struct CItemData 
		{
			HANDLE			_hContact;
			MAnnivDate*	_pDate;
			WORD				_wDaysBefore;
			BYTE				_wReminderState;

			CItemData( HANDLE hContact, MAnnivDate &date ) 
			{
				_hContact = hContact;
				_wReminderState = date.RemindOption();
				_wDaysBefore = date.RemindOffset();
				_pDate = new MAnnivDate( date );
			}

			~CItemData() 
			{
				if( _pDate ) 
				{
					// save changes
					if( _wReminderState != _pDate->RemindOption() || _wDaysBefore != _pDate->RemindOffset() ) 
					{
						_pDate->RemindOffset( _wDaysBefore );
						_pDate->RemindOption( _wReminderState );
						_pDate->DBWriteReminderOpts( _hContact );
					}
					delete _pDate;
					_pDate = NULL;
				}
			}
		};

		/**
		 * This class handles the movement of child controls on size change.
		 **/
		class CAnchor 
		{
		public:
			enum EAnchor
			{
				ANCHOR_LEFT = 1,
				ANCHOR_RIGHT = 2,
				ANCHOR_TOP = 4,
				ANCHOR_BOTTOM = 8,
				ANCHOR_ALL = ANCHOR_LEFT | ANCHOR_RIGHT | ANCHOR_TOP | ANCHOR_BOTTOM
			};

		private:
			WINDOWPOS*	_wndPos;
			HDWP				_hdWnds;
			RECT				_rcParent;
		
			VOID _ScreenToClient( HWND hWnd, LPRECT rc ) 
			{
				POINT pt = { rc->left, rc->top };
				
				ScreenToClient( hWnd, &pt );
				rc->right += pt.x - rc->left;
				rc->bottom += pt.y - rc->top;
				rc->left = pt.x;
				rc->top = pt.y;
			}

			VOID _MoveWindow( HWND hWnd, INT anchors ) 
			{
				if( !( _wndPos->flags & SWP_NOSIZE ) ) 
				{
					RECT rcc = _CalcPos( hWnd, anchors );
					MoveWindow( hWnd, rcc.left, rcc.top, rcc.right - rcc.left, rcc.bottom - rcc.top, FALSE );
				}
			}

			RECT _CalcPos( HWND hWnd, INT anchors ) 
			{
				RECT rcc;

				GetWindowRect( hWnd, &rcc );
				_ScreenToClient( _wndPos->hwnd, &rcc );
				if( !( _wndPos->flags & SWP_NOSIZE ) ) 
				{
					// calculate difference between new and old size
					const INT cx = _wndPos->cx - _rcParent.right + _rcParent.left;
					const INT cy = _wndPos->cy - _rcParent.bottom + _rcParent.top;

					if( cx != 0 || cy != 0 ) 
					{
						// move client rect points to the desired new position
						if( !( anchors & ANCHOR_LEFT ) || ( anchors & ANCHOR_RIGHT ) )
						{
							rcc.right += cx;
						}
						if( !( anchors & ANCHOR_TOP ) || ( anchors & ANCHOR_BOTTOM ) )
						{
							rcc.bottom += cy;
						}
						if( ( anchors & ANCHOR_RIGHT ) && ( !( anchors & ANCHOR_LEFT ) ) )
						{
							rcc.left += cx;
						}
						if( ( anchors & ANCHOR_BOTTOM ) && ( !( anchors & ANCHOR_TOP ) ) )
						{
							rcc.top += cy;
						}
					}
				}
				return rcc;
			}

		public:
			CAnchor( WINDOWPOS* wndPos, SIZE minSize ) 
			{
				GetWindowRect( wndPos->hwnd, &_rcParent );
				if( wndPos->cx < minSize.cx )
				{
					wndPos->cx = minSize.cx;
				}
				if( wndPos->cy < minSize.cy ) 
				{
					wndPos->cy = minSize.cy;
				}
				_wndPos = wndPos;
				_hdWnds = BeginDeferWindowPos( 2 );
			}
			
			~CAnchor() 
			{
				EndDeferWindowPos( _hdWnds );
			}
			
			VOID MoveCtrl( WORD idCtrl, INT anchors ) 
			{
				if( !( _wndPos->flags & SWP_NOSIZE ) ) 
				{
					HWND hCtrl = GetDlgItem( _wndPos->hwnd, idCtrl );
					RECT rcc = _CalcPos( hCtrl, anchors );
					_hdWnds = DeferWindowPos( _hdWnds, hCtrl, HWND_NOTOPMOST, rcc.left, rcc.top, rcc.right - rcc.left, rcc.bottom - rcc.top, SWP_NOZORDER );
				}
			}
		};

		/**
		 * This compare function is used by ListView_SortItemsEx to sort the listview.
		 *
		 * @param		iItem1	- index of the first item
		 * @param		iItem2	- index of the second item
		 * @param		pDlg		- pointer to the class' object
		 *
		 * @return	This function returns a number indicating comparison result.
		 **/
		static INT CALLBACK cmpProc( INT iItem1, INT iItem2, CAnnivList* pDlg )
		{
			INT result;

			if( pDlg ) 
			{
				TCHAR szText1[MAX_PATH];
				TCHAR szText2[MAX_PATH];

				switch( pDlg->_sortHeader ) 
				{
					case COLUMN_CONTACT:
					case COLUMN_PROTO:
					case COLUMN_DESC:
						{
							ListView_GetItemText( pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH );
							ListView_GetItemText( pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH );
							result = pDlg->_sortOrder * mir_tcscmp( szText1, szText2 );
						}
						break;

					case COLUMN_AGE:
					case COLUMN_ETA:
						{
							ListView_GetItemText( pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH );
							ListView_GetItemText( pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH );
							result = pDlg->_sortOrder * ( _ttoi( szText1 ) - _ttoi( szText2 ) );
						}
						break;

					case COLUMN_DATE: 
						{
							CItemData *id1 = pDlg->ItemData( iItem1 ), 
												*id2 = pDlg->ItemData( iItem2 );

							if( PtrIsValid( id1 ) && PtrIsValid( id2 ) ) 
							{
								result = pDlg->_sortOrder * id1->_pDate->Compare( *id2->_pDate );
								break;
							}
						}
					default:
						{
							result = 0;
						}
				}
			}
			else
			{
				result = 0;
			}
			return result;
		}

		/**
		 * This static method is the window procedure for the dialog.
		 *
		 * @param		hDlg	- handle of the dialog window
		 * @param		uMsg	- message to handle
		 * @param		wParam	- message dependend parameter
		 * @param		lParam	- message dependend parameter
		 *
		 * @return	depends on message
		 **/
		static INT_PTR CALLBACK DlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
		{
			CAnnivList *pDlg = ( CAnnivList* )GetUserData( hDlg );
			
			switch( uMsg ) 
			{
				case WM_INITDIALOG:
					{
						INT i = 0;
						HWND hCtrl;
						HICON hIcon;
						RECT rc;

						// link the class to the window handle
						pDlg = ( CAnnivList* )lParam;
						if( !pDlg )
						{
							break;
						}
						SetUserData( hDlg, lParam );
						pDlg->_hDlg = hDlg;

						// init pointer listview control
						pDlg->_hList = GetDlgItem( hDlg, EDIT_ANNIVERSARY_DATE );
						if( !pDlg->_hList )
						{
							break;
						}

						// set icons
						hIcon = NIcoLib::GetIcon( ICO_DLG_ANNIVERSARY );
						SendDlgItemMessage( hDlg, ICO_DLGLOGO, STM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIcon );
						SendMessage( hDlg, WM_SETICON, ICON_BIG, ( LPARAM )hIcon );

						// insert columns into the listboxes					
						ListView_SetExtendedListViewStyle( pDlg->_hList, LVS_EX_FULLROWSELECT );

						// add columns
						if( pDlg->AddColumn( CAnnivList::COLUMN_ETA,			LPGENT( "ETA" ), 40 ) ||
								pDlg->AddColumn( CAnnivList::COLUMN_CONTACT,	LPGENT( "Contact" ), 160 ) ||
								pDlg->AddColumn( CAnnivList::COLUMN_PROTO,		LPGENT( "Proto" ), 50 ) ||
								pDlg->AddColumn( CAnnivList::COLUMN_AGE,			LPGENT( "Age/Nr." ), 40 ) ||
								pDlg->AddColumn( CAnnivList::COLUMN_DESC,			LPGENT( "Anniversary" ), 100 ) ||
								pDlg->AddColumn( CAnnivList::COLUMN_DATE,			LPGENT( "Date" ), 80 ) 
							) 
						{
							break;
						}

						TranslateDialogDefault( hDlg );
						
						// save minimal size
						GetWindowRect( hDlg, &rc );
						pDlg->_sizeMin.cx = rc.right - rc.left;
						pDlg->_sizeMin.cy = rc.bottom - rc.top;
						
						// restore position and size
						Utils_RestoreWindowPosition( hDlg, NULL, MODNAME, "AnnivDlg_" );

						// add filter strings
						if( hCtrl = GetDlgItem( hDlg, COMBO_VIEW ) ) 
						{
							ComboBox_AddString( hCtrl, TranslateT( "All contacts") );
							ComboBox_AddString( hCtrl, TranslateT( "Birthdays only") );
							ComboBox_AddString( hCtrl, TranslateT( "Anniversaries only") );
							ComboBox_AddString( hCtrl, TranslateT( "Disabled reminder") );
							ComboBox_SetCurSel( hCtrl, pDlg->_filter.bFilterIndex );
						}

						// init reminder groups
						pDlg->_bRemindEnable = DB::Setting::GetByte( SET_REMIND_ENABLED, DEFVAL_REMIND_ENABLED ) != NServices::Reminder::REMIND_OFF;
						if( hCtrl = GetDlgItem( hDlg, CHECK_REMIND ) ) 
						{
							Button_SetCheck( hCtrl, pDlg->_bRemindEnable ? BST_INDETERMINATE : BST_UNCHECKED );
							EnableWindow( hCtrl, pDlg->_bRemindEnable );
						}

						CheckDlgButton( hDlg, CHECK_POPUP, DB::Setting::GetByte( SET_ANNIVLIST_POPUP, FALSE ) );
						// set number of days to show contact in advance
						SetDlgItemInt( hDlg, EDIT_DAYS, pDlg->_filter.wDaysBefore , FALSE );
						if( hCtrl = GetDlgItem( hDlg, CHECK_DAYS ) ) 
						{
							Button_SetCheck( hCtrl, DB::Setting::GetByte( SET_ANNIVLIST_FILTER_DAYSENABLED, FALSE ) );
							DlgProc( hDlg, WM_COMMAND, MAKEWPARAM( CHECK_DAYS, BN_CLICKED ), ( LPARAM )hCtrl );
						}
					}
					return TRUE;

				case WM_CTLCOLORSTATIC:
					switch( GetDlgCtrlID( ( HWND )lParam ) ) 
					{
						case STATIC_WHITERECT:
						case ICO_DLGLOGO:
						case TXT_NAME:
							{
								SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
								return ( BOOL )GetStockObject( WHITE_BRUSH );
							}
					}
					break;

				case WM_NOTIFY:
					switch( (( LPNMHDR )lParam )->idFrom ) 
					{
						case EDIT_ANNIVERSARY_DATE:
							switch( (( LPNMHDR )lParam )->code ) 
							{
								/*
								 * handle changed selection
								 */
								case LVN_ITEMCHANGED:
									{
										CItemData* pid;
										HWND hCheck;

										pDlg->_curSel = ( ( LPNMLISTVIEW )lParam )->iItem;
										pid = pDlg->ItemData( pDlg->_curSel );
										if( pid && pDlg->_bRemindEnable && ( hCheck = GetDlgItem( hDlg, CHECK_REMIND ) ) ) 
										{
											SetDlgItemInt( hDlg, EDIT_REMIND, pid->_wDaysBefore, FALSE );
											Button_SetCheck( hCheck, pid->_wReminderState );
											DlgProc( hDlg, WM_COMMAND, MAKEWPARAM( CHECK_REMIND, BN_CLICKED ), ( LPARAM )hCheck );
										}
									}
									break;

								/*
								 * resort the list
								 */
								case LVN_COLUMNCLICK:
									{
										LPNMLISTVIEW pnmv = ( LPNMLISTVIEW )lParam;

										if( pDlg->_sortHeader == pnmv->iSubItem ) 
										{
											pDlg->_sortOrder *= -1;
										}
										else 
										{
											pDlg->_sortOrder = 1;
											pDlg->_sortHeader = pnmv->iSubItem;
										}
										ListView_SortItemsEx( pDlg->_hList, ( CMPPROC )cmpProc, pDlg );
									}
									break;

								/*
								 * show contact menu
								 */
								case NM_RCLICK:
									{
										CItemData* pid = pDlg->ItemData( pDlg->_curSel );
										if( pid ) {
											HMENU hPopup = ( HMENU ) CallService( MS_CLIST_MENUBUILDCONTACT, ( WPARAM ) pid->_hContact, 0 );
											if( hPopup ) 
											{
												POINT pt;
												GetCursorPos( &pt );
												TrackPopupMenu( hPopup, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hDlg, NULL );
												DestroyMenu( hPopup );
											}
										}
									}
									break;

								/*
								 * handle double click on contact: show message dialog
								 */
								case NM_DBLCLK:
									{
										CItemData* pid = pDlg->ItemData( (( LPNMITEMACTIVATE )lParam )->iItem );
										if( pid )	
										{
											CallService( MS_MSG_SENDMESSAGE,( WPARAM )pid->_hContact, NULL );
										}
									}
									break;
							}
							break;
					}
					break;

				case WM_COMMAND:
					{
						if( PtrIsValid( pDlg ) ) 
						{
							CItemData* pid = pDlg->ItemData( pDlg->_curSel );
							
							// process contact menu command
							if( pid && CallService( MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM( LOWORD( wParam ), MPCF_CONTACTMENU ), ( LPARAM )pid->_hContact ) )
							{
								break;
							}
							
							switch( LOWORD( wParam ) ) 
							{

								/*
								 * enable/disable reminder checkbox is clicked
								 */
								case CHECK_REMIND: 
									{
										if( pDlg->_bRemindEnable && HIWORD( wParam ) == BN_CLICKED ) 
										{
											BOOLEAN checkState = Button_GetCheck( ( HWND )lParam );

											EnableWindow( GetDlgItem( hDlg, EDIT_REMIND ), checkState == BST_CHECKED );
											EnableWindow( GetDlgItem( hDlg, SPIN_REMIND ), checkState == BST_CHECKED );
											EnableWindow( GetDlgItem( hDlg, TXT_REMIND5 ), checkState == BST_CHECKED );
											if( pid && pid->_wReminderState != checkState ) 
											{
												pid->_wReminderState = checkState;
											}
										}
									}
									break;

								/*
								 * number of days to remind in advance is edited
								 */
								case EDIT_REMIND:
									{
										if( pid && pDlg->_bRemindEnable && HIWORD( wParam ) == EN_CHANGE ) 
										{
											WORD wDaysBefore = GetDlgItemInt( hDlg, LOWORD( wParam ), NULL, FALSE );
											if( pid->_wReminderState == BST_CHECKED && pid->_wDaysBefore != wDaysBefore ) 
											{
												pid->_wDaysBefore = wDaysBefore;
											}
										}
									}
									break;

								/*
								 * the filter to display only contacts which have an anniversary in a certain 
								 * period of time is enabled/disabled
								 */
								case CHECK_DAYS: 
									{
										if( HIWORD( wParam ) == BN_CLICKED ) 
										{
											BOOLEAN isChecked = Button_GetCheck( ( HWND )lParam );
											EnableWindow( GetDlgItem( hDlg, EDIT_DAYS ), isChecked );
											EnableWindow( GetDlgItem( hDlg, TXT_DAYS ), isChecked );
											pDlg->_filter.wDaysBefore = isChecked ? GetDlgItemInt( hDlg, EDIT_DAYS, NULL, FALSE ) : ( WORD )-1;
											pDlg->RebuildList();
										}
									}
									break;

								/*
								 * the number of days a contact must have an anniversary in advance to be displayed is edited
								 */
								case EDIT_DAYS:
									{
										if( HIWORD( wParam ) == EN_CHANGE ) 
										{
											WORD wNewDays = GetDlgItemInt( hDlg, LOWORD( wParam ), NULL, FALSE );
											if( wNewDays != pDlg->_filter.wDaysBefore ) 
											{
												pDlg->_filter.wDaysBefore = wNewDays;
												pDlg->RebuildList();
											}
										}
									}
									break;

								/*
								 * the filter selection of the filter combobox has changed
								 */
								case COMBO_VIEW:
									{
										if( HIWORD( wParam ) == CBN_SELCHANGE ) 
										{
											pDlg->_filter.bFilterIndex = ComboBox_GetCurSel( ( HWND )lParam );
											pDlg->RebuildList();
										}
									}
							}
						}
					}
					break;

				case WM_DRAWITEM:
					return CallService( MS_CLIST_MENUDRAWITEM, wParam, lParam );

				case WM_MEASUREITEM:
					return CallService( MS_CLIST_MENUMEASUREITEM, wParam, lParam );
				
				case WM_WINDOWPOSCHANGING:
					{
						if( PtrIsValid( pDlg ) ) 
						{
							CAnchor anchor( ( WINDOWPOS* )lParam, pDlg->_sizeMin );
							INT anchorPos;

							anchor.MoveCtrl( ICO_DLGLOGO, CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_TOP );
							anchorPos = CAnchor::ANCHOR_LEFT | CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_TOP;
							anchor.MoveCtrl( STATIC_LINE1, anchorPos );
							anchor.MoveCtrl( STATIC_WHITERECT, anchorPos );
							anchor.MoveCtrl( GROUP_STATS, anchorPos );

							// birthday list
							anchor.MoveCtrl( EDIT_ANNIVERSARY_DATE, CAnchor::ANCHOR_ALL );

							anchorPos = CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_BOTTOM;
							
							// filter group
							anchor.MoveCtrl( GROUP_FILTER, anchorPos );
							anchor.MoveCtrl( COMBO_VIEW, anchorPos );
							anchor.MoveCtrl( CHECK_DAYS, anchorPos );
							anchor.MoveCtrl( EDIT_DAYS, anchorPos );
							anchor.MoveCtrl( TXT_DAYS, anchorPos );

							// filter group
							anchor.MoveCtrl( GROUP_REMINDER, anchorPos );
							anchor.MoveCtrl( CHECK_REMIND, anchorPos );
							anchor.MoveCtrl( EDIT_REMIND, anchorPos );
							anchor.MoveCtrl( SPIN_REMIND, anchorPos );
							anchor.MoveCtrl( TXT_REMIND6, anchorPos );
							anchor.MoveCtrl( CHECK_POPUP, anchorPos );
						}
					}
					break;

				/**
				 * This message is sent if eighter the user clicked on the close button or
				 * Miranda fires the ME_SYSTEM_SHUTDOWN event.
				 **/
				case WM_CLOSE:
					{
						DestroyWindow( hDlg );
					}
					break;

				/**
				 * If the anniversary list is destroyed somehow, the data class must be
				 * deleted, too.
				 **/
				case WM_DESTROY:
					{
						if( PtrIsValid( pDlg ) )
						{
							SetUserData( hDlg, NULL );
							delete pDlg;
						}
						MagneticWindows_RemoveWindow(hDlg);
					}
					break;
			}
			return FALSE;
		}

		/**
		 * This method adds a column to the listview.
		 *
		 * @param		iSubItem			- desired column index
		 * @param		pszText				- the header text
		 * @param		defaultWidth	- the default witdth
		 *
		 * @retval	0 if successful
		 * @retval	1 if failed
		 **/
		BOOLEAN AddColumn( INT iSubItem, LPCTSTR pszText, INT defaultWidth ) 
		{
			LVCOLUMN lvc;
			CHAR szSetting[MAXSETTING];

			mir_snprintfA( szSetting, SIZEOF( szSetting ), "AnnivDlg_Col%d", iSubItem );
			lvc.cx = DB::Setting::GetWord( szSetting, defaultWidth );
			lvc.mask = LVCF_WIDTH|LVCF_TEXT;
			lvc.iSubItem = iSubItem;
			lvc.pszText = TranslateTS( pszText );
			return ListView_InsertColumn( _hList, lvc.iSubItem++, &lvc ) == -1;
		}

		/**
		 * This method sets the subitem text for the current contact
		 *
		 * @param		iItem			- index of the current row
		 * @param		iSubItem	- column to set text for
		 * @param		pszText		- text to insert
		 *
		 * @retval	TRUE if successful
		 * @retval	FALSE if failed
		 **/
		BOOLEAN AddSubItem( INT iItem, INT iSubItem, LPTSTR pszText ) 
		{
			LVITEM lvi;
			if( iSubItem > 0 ) 
			{
				lvi.iItem = iItem;
				lvi.iSubItem = iSubItem;
				lvi.pszText = pszText;
				lvi.mask = LVIF_TEXT;
				return ListView_SetItem( _hList, &lvi );
			}
			return FALSE;
		}

		/**
		 * This method adds a row to the listview.
		 *
		 * @param		pszText	- text to insert
		 * @param		lParam	- pointer to the rows data
		 *
		 * @retval	TRUE if successful
		 * @retval	FALSE if failed
		 **/	
		BOOLEAN AddItem( LPTSTR pszText, LPARAM lParam )
		{
			LVITEM lvi;

			if( !pszText )
			{
				return FALSE;
			}
			lvi.iItem = 0;
			lvi.iSubItem = 0;
			lvi.pszText = pszText;
			lvi.mask = LVIF_TEXT|TVIF_PARAM;
			lvi.lParam = lParam;
			return ListView_InsertItem( _hList, &lvi );
		}

		/**
		 * This method adds a row to the listview.
		 *
		 * @param			hContact		- contact to add the line for
		 * @param			pszProto		- contact's protocol
		 * @param			ad					- anniversary to add
		 * @param			mtNow				- current time
		 * @param			wDaysBefore	- number of days in advance to remind the user of the anniversary
		 *
		 * @retval	TRUE if successful
		 * @retval	FALSE if failed
		 **/
		BOOLEAN AddRow( HANDLE hContact, LPCSTR pszProto, MAnnivDate &ad, MTime &mtNow, WORD wDaysBefore ) 
		{
			TCHAR tszText[MAX_PATH];
			INT diff, iItem = -1;
			CItemData *pdata;
		
			//
			// first column: ETA
			//
			diff = ad.CompareDays( mtNow );
			if( diff < 0 ) 
			{
				diff += IsLeap( mtNow.Year() + 1 ) ? 366 : 365;
			}
			// is filtered
			if( diff <= _filter.wDaysBefore ) 
			{
				// read reminder options for the contact
				ad.DBGetReminderOpts( hContact );
				if( ( _filter.bFilterIndex != FILTER_DISABLED_REMINDER ) || ( ad.RemindOption() == BST_UNCHECKED ) )
				{
					// set default offset if required
					if( ad.RemindOffset() == ( WORD )-1 )
					{
						ad.RemindOffset( wDaysBefore );
						
						// create data object
						pdata = new CItemData( hContact, ad );
						if( !pdata ) 
						{
							return FALSE;
						}
						// add item
						iItem = AddItem( _itot( diff, tszText, 10 ), ( LPARAM )pdata );
						if( iItem == -1 ) 
						{
							delete pdata;
							return FALSE;
						}
						// second column: contact name
						AddSubItem( iItem, COLUMN_CONTACT, DB::Contact::DisplayName( hContact ) );

						// third column: protocol
						TCHAR* ptszProto = mir_a2t( pszProto );
						AddSubItem( iItem, COLUMN_PROTO, ptszProto );
						mir_free(ptszProto);

						// forth line: age
						AddSubItem( iItem, COLUMN_AGE, _itot( ad.Age( &mtNow ), tszText, 10 ) );

						// fifth line: anniversary
						AddSubItem( iItem, COLUMN_DESC, ( LPTSTR )ad.Description() );

						// sixth line: date
						ad.DateFormat( tszText, SIZEOF( tszText ) );
						AddSubItem( iItem, COLUMN_DATE, tszText );
						
						_numRows++;
						//mir_free( ptszText );
					}
				}
			}
			return TRUE;
		}

		/**
		 * This method clears the list and adds contacts again, according to the current filter settings.
		 **/
		VOID RebuildList() 
		{
			HANDLE hContact;
			LPSTR pszProto;
			MTime mtNow;
			MAnnivDate ad;
			INT	i = 0;
			DWORD age = 0;
			WORD wDaysBefore = DB::Setting::GetWord( SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET );
			WORD numMale = 0;
			WORD numFemale = 0;
			WORD numContacts = 0;
			WORD numBirthContacts = 0;

			ShowWindow( _hList, SW_HIDE );
			DeleteAllItems();
			mtNow.GetLocalTime();

			// insert the items into the list
			for( hContact = DB::Contact::FindFirst();
					 hContact;
					 hContact = DB::Contact::FindNext( hContact ) )
			{
				// ignore meta subcontacts here, as they are not interesting.
				if( !DB::MetaContact::IsSub( hContact ) )
				{
					// filter protocol
					pszProto = DB::Contact::Proto( hContact );
					if( pszProto ) 
					{
						numContacts++;
						switch( NServices::Gender::Get( hContact, pszProto ) )
						{
						case 'M': 
							{
								numMale++;
							}
							break;

						case 'F': 
							{
								numFemale++; 
							}
						}

						if( !ad.DBGetBirthDate( hContact, pszProto ) ) 
						{
							age += ad.Age( &mtNow );
							numBirthContacts++;

							// add birthday
							if( ( _filter.bFilterIndex != FILTER_ANNIV ) && ( !_filter.pszProto || !strcmpi( pszProto, _filter.pszProto ) )	)
							{	
								AddRow( hContact, pszProto, ad, mtNow, wDaysBefore );
							}
						}

						// add anniversaries
						if( _filter.bFilterIndex != FILTER_BIRTHDAY && ( !_filter.pszProto || !strcmpi( pszProto, _filter.pszProto ) ) ) 
						{
							for( i = 0; !ad.DBGetAnniversaryDate( hContact, i ); i++ ) 
							{
								if( !_filter.pszAnniv || !_tcsicmp( _filter.pszAnniv, ad.Description() ) )
								{
									AddRow( hContact, pszProto, ad, mtNow, wDaysBefore );
								}
							}
						}
					}
				}
			}
			ListView_SortItemsEx( _hList, ( CMPPROC )cmpProc, this );
			ShowWindow( _hList, SW_SHOW );

			// display statistics
			SetDlgItemInt( _hDlg, TXT_NUMBIRTH, numBirthContacts, FALSE );
			SetDlgItemInt( _hDlg, TXT_NUMCONTACT, numContacts, FALSE );
			SetDlgItemInt( _hDlg, TXT_FEMALE, numFemale, FALSE );
			SetDlgItemInt( _hDlg, TXT_MALE, numMale, FALSE );
			SetDlgItemInt( _hDlg, TXT_AGE, numBirthContacts > 0 ? max( 0, ( age - ( age % numBirthContacts ) ) / numBirthContacts ) : 0, FALSE );
		}

		/**
		 * This method deletes all items from the listview
		 **/	
		VOID DeleteAllItems() 
		{
			CItemData *pid;
			
			for( INT i = 0; i < _numRows; i++ ) 
			{
				pid = ItemData( i );
				if( pid ) 
				{
					delete pid;
				}
			}
			ListView_DeleteAllItems( _hList );
			_numRows = 0;
		}

		/**
		 * This method returns the data structure accociated with a list item.
		 *
		 * @param		iItem	- index of the desired item
		 *
		 * @return	pointer to the data strucutre on success or NULL otherwise.
		 **/
		CItemData* ItemData( INT iItem ) 
		{
			if( _hList && iItem >= 0 && iItem < _numRows ) 
			{
				LVITEM lvi;
			
				lvi.mask = LVIF_PARAM;
				lvi.iItem = iItem;
				lvi.iSubItem = 0;
				if( ListView_GetItem( _hList, &lvi ) && PtrIsValid( lvi.lParam ) )
				{
					return ( CItemData* )lvi.lParam;
				}
			}
			return NULL;
		}

		/**
		 * This method loads all filter settings from db
		 **/	
		VOID LoadFilter() 
		{
			_filter.wDaysBefore = DB::Setting::GetWord( SET_ANNIVLIST_FILTER_DAYS, 9 );
			_filter.bFilterIndex = DB::Setting::GetByte( SET_ANNIVLIST_FILTER_INDEX, 0 );
		}

		/**
		 * This method saves all filter settings to db
		 **/	
		VOID SaveFilter() 
		{
			if( _hDlg ) 
			{
				DB::Setting::WriteWord( SET_ANNIVLIST_FILTER_DAYS, ( WORD )GetDlgItemInt( _hDlg, EDIT_DAYS, NULL, FALSE ) );
				DB::Setting::WriteByte( SET_ANNIVLIST_FILTER_DAYSENABLED, ( BYTE )Button_GetCheck( GetDlgItem( _hDlg, CHECK_DAYS ) ) );
				DB::Setting::WriteByte( SET_ANNIVLIST_FILTER_INDEX, ( BYTE )ComboBox_GetCurSel( GetDlgItem( _hDlg, EDIT_DAYS ) ) );
			}
		}

	public:

		/**
		 * This is the default constructor.
		 **/
		CAnnivList() 
		{
			_hList = NULL;
			_sortHeader = 0;
			_sortOrder = 1;
			_curSel = -1;
			_numRows = 0;
			LoadFilter();

			_hDlg = CreateDialogParam( ghInst, MAKEINTRESOURCE( IDD_ANNIVERSARY_LIST ), NULL, ( DLGPROC )DlgProc, ( LPARAM )this );
			if( _hDlg )
			{
				MagneticWindows_AddWindow(_hDlg);
				_mHookExit = HookEventMessage( ME_SYSTEM_PRESHUTDOWN, _hDlg, WM_CLOSE );
			}
			else
			{
				_mHookExit = NULL;
				delete this;
			}
		}

		/**
		 * This is the default destructor.
		 **/
		~CAnnivList() 
		{
			// delete the shutdown hook
			if( _mHookExit )
			{
				UnhookEvent( _mHookExit );
				_mHookExit = NULL;
			}

			// close window if required
			if( _hDlg ) 
			{
				// save list state
				if( _hList ) 
				{
					CHAR szSetting[MAXSETTING];
					INT c, cc = Header_GetItemCount( ListView_GetHeader( _hList ) );

					for( c = 0; c < cc; c++ )
					{
						mir_snprintfA( szSetting, MAXSETTING, "AnnivDlg_Col%d", c );
						DB::Setting::WriteWord( szSetting, ( WORD )ListView_GetColumnWidth( _hList, c ) );
					}
					DeleteAllItems();
				}
				// remember popup setting
				DB::Setting::WriteByte( SET_ANNIVLIST_POPUP, ( BYTE )IsDlgButtonChecked( _hDlg, CHECK_POPUP ) );
				// save window position, size and column widths
				Utils_SaveWindowPosition( _hDlg, NULL, MODNAME, "AnnivDlg_" );
				SaveFilter();

				// if the window did not yet retrieve a WM_DESTROY message, do it right now.
				if( PtrIsValid( GetUserData( _hDlg ) ) )
				{
					SetUserData( _hDlg, NULL );
					DestroyWindow( _hDlg );
				}
			}	
			gpDlg = NULL;
		}

		/**
		 * name:	BringToFront
		 * class:	CAnnivList
		 * desc:	brings anniversary list to the top
		 * param:	none
		 * return:	nothing
		 **/
		VOID BringToFront()
		{
			ShowWindow( _hDlg, SW_RESTORE );
			SetForegroundWindow( _hDlg );
			SetFocus( _hDlg );
		}

	}; // class CAnnivList

	/***********************************************************************************************************
	 * service handlers
	 ***********************************************************************************************************/

	/**
	 * This is the service function that is called list all anniversaries.
	 *
	 * @param	wParam	- not used
	 * @param	lParam	- not used
	 *
	 * @return	always 0
	 **/
	INT ShowDialog( WPARAM wParam, LPARAM lParam ) 
	{
		if( !gpDlg )
		{
			try 
			{
				gpDlg = new CAnnivList();
			}
			catch( ... ) 
			{
				delete gpDlg;
				gpDlg = NULL;
			}
		} 
		else 
		{
			gpDlg->BringToFront();
		}
		return 0;
	}

	/***********************************************************************************************************
	 * loading and unloading module
	 ***********************************************************************************************************/

	#define TBB_IDBTN		"AnnivList"
	#define TBB_ICONAME	TOOLBARBUTTON_ICONIDPREFIX TBB_IDBTN TOOLBARBUTTON_ICONIDPRIMARYSUFFIX

	/**
	 * This function is called by the ME_TTB_MODULELOADED event.
	 * It adds a set of buttons to the TopToolbar plugin.
	 *
	 * @param		wParam	- none
	 *
	 * @return	nothing
	 **/
	int hTTButton = -1;
	VOID OnTopToolBarLoaded()
	{
		HICON hIcon = NIcoLib::RegisterIcon( TBB_ICONAME, "Anniversary list", SECT_TOOLBAR, IDI_ANNIVERSARY, 0 );
		if( hIcon )
		{
			TTBButtonV2 ttb;
			ZeroMemory( &ttb, sizeof( TTBButtonV2 ) );
			ttb.cbSize = sizeof( TTBButtonV2 );

			ttb.name = Translate( "Anniversary list" );
			ttb.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP;
			ttb.pszServiceDown = MS_USERINFO_REMINDER_LIST;
			ttb.hIconUp = ttb.hIconDn = hIcon;
			ttb.tooltipUp = Translate( "Anniversary list" );


			hTTButton = CallService( MS_TTB_ADDBUTTON, ( WPARAM ) &ttb, 0 );
			if( hTTButton )
				CallService(MS_TTB_SETBUTTONOPTIONS, MAKEWPARAM(TTBO_TIPNAME, hTTButton), (LPARAM)(Translate( "Anniversary list" )));
		}
	}

	/**
	 * This function is called by the ME_TB_MODULELOADED event.
	 * It adds a set of buttons to the Toolbar of the Modern Contact List.
	 *
	 * @param		wParam	- none
	 *
	 * @return	nothing
	 **/
	VOID OnToolBarLoaded()
	{
		TBButton tbb;

		ZeroMemory( &tbb, sizeof( tbb ) );
		tbb.cbSize = sizeof( tbb );
		tbb.tbbFlags = TBBF_VISIBLE | TBBF_SHOWTOOLTIP;
		tbb.defPos = 2100;
		tbb.pszButtonName = 
		tbb.pszButtonID = TBB_IDBTN;
		tbb.pszServiceName = MS_USERINFO_REMINDER_LIST;
		tbb.pszTooltipDn =
		tbb.pszTooltipUp = LPGEN( "Anniversary list" );
		tbb.hPrimaryIconHandle = 
		tbb.hSecondaryIconHandle = NIcoLib::RegisterIconHandle( TBB_ICONAME, tbb.pszButtonName, SECT_TOOLBAR, IDI_ANNIVERSARY, 0 );

		CallService( MS_TB_ADDBUTTON, 0, ( LPARAM ) &tbb );
	}

	/**
	 * This function initially loads all required stuff for the anniversary list.
	 *
	 * @param		none
	 *
	 * @return	nothing
	 **/
	VOID LoadModule()
	{
		HOTKEYDESC hk;

		CreateServiceFunction( MS_USERINFO_REMINDER_LIST, ShowDialog );

		hk.cbSize = sizeof( HOTKEYDESC );
		hk.lParam = NULL;
		hk.DefHotKey = NULL;
		hk.pszSection = MODNAME;
		hk.pszName = "AnniversaryList";
		hk.pszDescription = LPGEN( "Popup Anniversary list" );
		hk.pszService = MS_USERINFO_REMINDER_LIST;
		CallService( MS_HOTKEY_REGISTER, NULL, ( LPARAM )&hk );
	}

} // namespace NAnniversaryList